package be.android.forap.crypto;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.SignatureException;
import java.util.Iterator;

import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.spongycastle.jce.provider.BouncyCastleProvider;

import android.util.Log;

public class PGPDigitalSignature {

	public static final int HASH_ALGORITHM = PGPUtil.SHA256;

	private static void initialize(){
		if (Security.getProvider("SC") == null)
			Security.addProvider(new BouncyCastleProvider());          
	}


	/*
	 * create a clear text signed file.
	 */
	public static byte[] signData(byte[] in, byte[] key, char[] pass)
					throws IOException, NoSuchAlgorithmException, NoSuchProviderException, PGPException, SignatureException { 
		InputStream secretKey =  new ByteArrayInputStream(key);
		
		initialize();
		final byte[] endline = "\r\n".getBytes("UTF-8");

		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		outputStream.write(in);
		outputStream.write(endline);
		outputStream.write(endline);

		byte inStream[] = outputStream.toByteArray();

		
		int digest = HASH_ALGORITHM;

		PGPSecretKey                    pgpSecKey = readSecretKey(secretKey);
		PGPPrivateKey                   pgpPrivKey = pgpSecKey.extractPrivateKey(pass, "SC");        
		PGPSignatureGenerator           sGen = new PGPSignatureGenerator(pgpSecKey.getPublicKey().getAlgorithm(), digest, "SC");
		PGPSignatureSubpacketGenerator  spGen = new PGPSignatureSubpacketGenerator();

		sGen.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);

		Iterator    it = pgpSecKey.getPublicKey().getUserIDs();
		if (it.hasNext())
		{
			spGen.setSignerUserID(false, (String)it.next());
			sGen.setHashedSubpackets(spGen.generate());
		}

        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();

		OutputStream out = byteOut;
        
		InputStream fIn = new BufferedInputStream(new ByteArrayInputStream(inStream));
		ArmoredOutputStream aOut = new ArmoredOutputStream(out);

		aOut.beginClearText(digest);

		//
		// note the last \n/\r/\r\n in the file is ignored
		//
		ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
		int lookAhead = readInputLine(lineOut, fIn);

		processLine(aOut, sGen, lineOut.toByteArray());

		if (lookAhead != -1)
		{
			do
			{
				lookAhead = readInputLine(lineOut, lookAhead, fIn);

				sGen.update((byte)'\r');
				sGen.update((byte)'\n');

				processLine(aOut, sGen, lineOut.toByteArray());
			}
			while (lookAhead != -1);
		}
		
		fIn.close();

		aOut.endClearText();

		BCPGOutputStream bOut = new BCPGOutputStream(aOut);

		sGen.generate().encode(bOut);

		aOut.close();
		
		secretKey.close();
		
		return byteOut.toByteArray();
	}

	public static boolean verifySignedData(byte[] data, byte[] key , ByteArrayOutputStream message)
			throws IOException, PGPException, NoSuchProviderException, SignatureException{
		initialize();
		InputStream in = new ByteArrayInputStream(data);
		InputStream keyIn = new ByteArrayInputStream(key);

		ArmoredInputStream    aIn = new ArmoredInputStream(in);
		ByteArrayOutputStream out = new ByteArrayOutputStream();

		//
		// write out signed section using the local line separator.
		// note: trailing white space needs to be removed from the end of
		// each line RFC 4880 Section 7.1
		//
		ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
		int                   lookAhead = readInputLine(lineOut, aIn);
		byte[]                lineSep = getLineSeparator();

		if (lookAhead != -1 && aIn.isClearText())
		{
			byte[] line = lineOut.toByteArray();
			out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
			out.write(lineSep);

			while (lookAhead != -1 && aIn.isClearText())
			{
				lookAhead = readInputLine(lineOut, lookAhead, aIn);

				line = lineOut.toByteArray();
				out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
				out.write(lineSep);
			}
		}

		out.close();
		
		message.write(new String(out.toByteArray()).trim().getBytes());

		PGPPublicKeyRingCollection pgpRings = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
		PGPObjectFactory           pgpFact = new PGPObjectFactory(aIn);
		//PGPSignatureList    p3 = null;
		/*Log.e("SIGN","3");
		
		Object    o = pgpFact.nextObject();
        /*if (o instanceof PGPCompressedData)
        {
            PGPCompressedData             c1 = (PGPCompressedData)o;

            pgpFact = new PGPObjectFactory(c1.getDataStream());
            
            p3 = (PGPSignatureList)pgpFact.nextObject();
        }
        else
        {
            p3 = (PGPSignatureList)o;
        }*/
	    PGPSignatureList           p3 = (PGPSignatureList)pgpFact.nextObject();
		PGPSignature               sig = p3.get(0);

		PGPPublicKey pk = pgpRings.getPublicKey(sig.getKeyID());
		if(pk==null){
			Log.e("ver","pk is null.");
			return false;			
		}
		sig.initVerify(pk, "SC");

		//
		// read the input, making sure we ignore the last newline.
		//

		InputStream sigIn = new ByteArrayInputStream(out.toByteArray());//new FileInputStream(resultName));

		lookAhead = readInputLine(lineOut, sigIn);

		processLine(sig, lineOut.toByteArray());

		if (lookAhead != -1)
		{
			do
			{
				lookAhead = readInputLine(lineOut, lookAhead, sigIn);

				sig.update((byte)'\r');
				sig.update((byte)'\n');

				processLine(sig, lineOut.toByteArray());
			}
			while (lookAhead != -1);
		}

		sigIn.close();
		in.close();
		keyIn.close();
		if (sig.verify())
		{
			Log.e("ver","signature verified.");
			return true;
		}
		else
		{
			Log.e("ver","signature verification failed.");
			return false;
		}
	}

	
	/**
	 * A simple routine that opens a key ring file and loads the first available key
	 * suitable for signature generation.
	 * 
	 * @param input stream to read the secret key ring collection from.
	 * @return a secret key.
	 * @throws IOException on a problem with using the input stream.
	 * @throws PGPException if there is an issue parsing the input stream.
	 */
	static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException{
		PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
				PGPUtil.getDecoderStream(input));

		//
		// we just loop through the collection till we find a key suitable for encryption, in the real
		// world you would probably want to be a bit smarter about this.
		//

		Iterator keyRingIter = pgpSec.getKeyRings();
		while (keyRingIter.hasNext())
		{
			PGPSecretKeyRing keyRing = (PGPSecretKeyRing)keyRingIter.next();

			Iterator keyIter = keyRing.getSecretKeys();
			while (keyIter.hasNext())
			{
				PGPSecretKey key = (PGPSecretKey)keyIter.next();

				if (key.isSigningKey())
				{
					return key;
				}
			}
		}

		throw new IllegalArgumentException("Can't find signing key in key ring.");
	}
	/**
	 * 
	 * 
	 */

	private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
			throws IOException
			{
		bOut.reset();

		int lookAhead = -1;
		int ch;

		while ((ch = fIn.read()) >= 0)
		{
			bOut.write(ch);
			if (ch == '\r' || ch == '\n')
			{
				lookAhead = readPassedEOL(bOut, ch, fIn);
				break;
			}
		}

		return lookAhead;
			}

	private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
			throws IOException
			{
		bOut.reset();

		int ch = lookAhead;

		do
		{
			bOut.write(ch);
			if (ch == '\r' || ch == '\n')
			{
				lookAhead = readPassedEOL(bOut, ch, fIn);
				break;
			}
		}
		while ((ch = fIn.read()) >= 0);

		if (ch < 0)
		{
			lookAhead = -1;
		}

		return lookAhead;
			}

	private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
			throws IOException
			{
		int lookAhead = fIn.read();

		if (lastCh == '\r' && lookAhead == '\n')
		{
			bOut.write(lookAhead);
			lookAhead = fIn.read();
		}

		return lookAhead;
			}
	private static void processLine(PGPSignature sig, byte[] line)
			throws SignatureException, IOException
			{
		int length = getLengthWithoutWhiteSpace(line);
		if (length > 0)
		{
			sig.update(line, 0, length);
		}
			}

	private static void processLine(OutputStream aOut, PGPSignatureGenerator sGen, byte[] line)
			throws SignatureException, IOException
			{
		// note: trailing white space needs to be removed from the end of
		// each line for signature calculation RFC 4880 Section 7.1
		int length = getLengthWithoutWhiteSpace(line);
		if (length > 0)
		{
			sGen.update(line, 0, length);
		}

		aOut.write(line, 0, line.length);
	}

	private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line)
	{
		int    end = line.length - 1;

		while (end >= 0 && isWhiteSpace(line[end]))
		{
			end--;
		}

		return end + 1;
	}

	private static boolean isLineEnding(byte b)
	{
		return b == '\r' || b == '\n';
	}

	private static int getLengthWithoutWhiteSpace(byte[] line)
	{
		int    end = line.length - 1;

		while (end >= 0 && isWhiteSpace(line[end]))
		{
			end--;
		}

		return end + 1;
	}

	private static boolean isWhiteSpace(byte b)
	{
		return isLineEnding(b) || b == '\t' || b == ' ';
	}

	private static byte[] getLineSeparator()
	{
		String nl = System.getProperty("line.separator");
		byte[] nlBytes = new byte[nl.length()];

		for (int i = 0; i != nlBytes.length; i++)
		{
			nlBytes[i] = (byte)nl.charAt(i);
		}

		return nlBytes;
	}


}
